package logic

import (
	"MoSkeleton/config/consts"
	"MoSkeleton/framework"
	"MoSkeleton/framework/core/config/properties_db"
	consts2 "MoSkeleton/framework/core/consts"
	"MoSkeleton/framework/core/moerrors"
	"MoSkeleton/framework/mo_gorm"
	"MoSkeleton/framework/utils"
	"MoSkeleton/global"
	"MoSkeleton/models/tool"
	"MoSkeleton/services/tool/logic/internal"
	tool2 "MoSkeleton/web/pogos/response/tool"
	"bytes"
	"go.uber.org/zap"
	"golang.org/x/text/cases"
	"golang.org/x/text/language"
	"gorm.io/gorm"
	"html/template"
	"io/fs"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

var (
	reqMapPersistentDbType = map[string]string{
		"mysql":      "mysql",
		"postgresql": "pgsql",
		"sqlite":     "sqlite",
		"oracle":     "oracle",
		"mssql":      "mssql",
	}
)

const (
	TplFileSuffix = ".tpl"
)

func TestConnection(datasourcePogo *DatasourcePogo) error {
	persistentDb, err := tranDatasourceAddReqToPersistentDb(datasourcePogo)
	if err != nil {
		return err
	}
	_, err = mo_gorm.GenDbByPersistentDb(persistentDb, true)
	return err
}

func NewGenDb(datasourcePogo *DatasourcePogo) (db *gorm.DB, err error) {
	persistentDb, err := tranDatasourceAddReqToPersistentDb(datasourcePogo)
	if err != nil {
		return nil, err
	}
	return mo_gorm.GenDbByPersistentDb(persistentDb, false)
}

func InitGenTableForNew(genTable *tool.GenTable, genBiz *tool.GenBiz) {
	isIgnoreTablePrefix := genBiz.IsIgnoreTablePrefix == consts.U_YES
	genTable.StructName = internal.TableNameToStructName(genTable.SourceTableName, isIgnoreTablePrefix, genBiz.TableSuffix)
	genTable.TablePrefix = internal.TablePrefixFromName(genTable.SourceTableName)
	genTable.PackageName = genTable.ModuleName
	genTable.BusinessName = businessNameByGenTable(genTable)
	genTable.FunctionName = functionNameByGenTable(genTable)
	genTable.TplCategory = consts.TPL_CRUD
}

// 生成聚合式的代码
func GenCodeAsAggregate(classifyCode string, codeGeneratorAggregateResp *tool2.CodeGeneratorAggregateResp, tplChannel chan<- *TplFileBuffer) error {
	classifyPath := filepath.Join(framework.WorkDir, consts2.CodeGenPath, classifyCode)
	for _, fileName := range consts.TplFilesByAggregate {
		tplFilePath := filepath.Join(classifyPath, fileName)
		_, err := os.Stat(tplFilePath)
		if os.IsNotExist(err) {
			global.MoLogger.Debug("文件不存在：", zap.String("file", tplFilePath))
			return nil
		} else if err != nil {
			return err
		}
		tplFileBuffer, err := genTplBufferByAggregateFile(tplFilePath, codeGeneratorAggregateResp)
		if err != nil {
			return err
		}
		tplChannel <- tplFileBuffer
	}
	return nil
}

func GenCodeAsModuleAggregate(classifyCode string, codeGeneratorAggregateResp *tool2.CodeGeneratorModuleAggregateItemResp, tplChannel chan<- *TplFileBuffer) error {
	classifyPath := filepath.Join(framework.WorkDir, consts2.CodeGenPath, classifyCode)
	for _, fileName := range consts.TplFileByModuleAggregate {
		tplFilePath := filepath.Join(classifyPath, fileName)
		_, err := os.Stat(tplFilePath)
		if os.IsNotExist(err) {
			global.MoLogger.Debug("文件不存在：", zap.String("file", tplFilePath))
			return nil
		} else if err != nil {
			return err
		}
		tplFileBuffer, err := genTplBufferByModuleAggregateFile(tplFilePath, codeGeneratorAggregateResp)
		if err != nil {
			return err
		}
		tplChannel <- tplFileBuffer
	}
	return nil
}

// 按表生成代码
func GenCodeAsTable(classifyCode string, codeGeneratorResp *tool2.CodeGeneratorResp, tplChannel chan<- *TplFileBuffer) error {
	classifyPath := filepath.Join(framework.WorkDir, consts2.CodeGenPath, classifyCode)
	err := filepath.WalkDir(classifyPath, func(path string, info fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if classifyPath == path || info.IsDir() {
			return nil
		}
		if filepath.Ext(path) != TplFileSuffix {
			return nil
		}
		dir, file := filepath.Split(path)
		framework.MoLogger.Debug("目录: %s", zap.String("dir", dir))
		idx := utils.SliceFindIndex(consts.TplFilesByTable, file)
		if idx == -1 {
			return nil
		}
		var tplFileBuffer *TplFileBuffer
		tplFileBuffer, err = genTplBufferByFile(path, codeGeneratorResp)
		if err != nil {
			return err
		}
		tplChannel <- tplFileBuffer
		return nil
	})
	return err
}

// 将数据列类型，映射成go类型
func ConvertToGoType(dataSourceType string, columnType string) string {
	goType := convertCommonToGoType(columnType)
	if goType != "" {
		return goType
	}
	switch dataSourceType {
	case "mysql":
		return convertMySQLToGoType(columnType)
	case "postgres":
		return convertPostgresToGoType(columnType)
	// 可以添加其他数据库类型的转换逻辑
	default:
		return "unknown"
	}
}

func ConvertToGoFieldName(columnName string) string {
	splitNames := strings.Split(columnName, "_")
	tc := cases.Title(language.Und)
	for i, splitName := range splitNames {
		splitNames[i] = tc.String(splitName)
	}
	return strings.Join(splitNames, "")
}

func ConvertToJsonFieldName(columnName string) string {
	splitNames := strings.Split(columnName, "_")
	tcTitle := cases.Title(language.Und)
	tcLower := cases.Lower(language.Und)
	for i, splitName := range splitNames {
		if i == 0 {
			splitNames[i] = tcLower.String(splitName)
		} else {
			splitNames[i] = tcTitle.String(splitName)
		}
	}
	return strings.Join(splitNames, "")
}
func convertCommonToGoType(columnType string) string {
	switch {
	case strings.HasPrefix(columnType, "int"):
		return "int"
	case strings.HasPrefix(columnType, "bigint"):
		return "uint64"
	case strings.HasPrefix(columnType, "smallint"):
		return "int16"
	case strings.HasPrefix(columnType, "float"):
		return "float32"
	case strings.HasPrefix(columnType, "double"):
		return "float64"
	case strings.HasPrefix(columnType, "char"):
		return "string"
	case strings.HasPrefix(columnType, "varchar"):
		return "string"
	case strings.HasPrefix(columnType, "text"):
		return "string"
	case strings.HasPrefix(columnType, "timestamp"):
		return "uint64"
	}
	return ""
}

// MySQL 数据类型转换
func convertMySQLToGoType(columnType string) string {
	switch {
	case strings.HasPrefix(columnType, "tinyint(1)"):
		return "bool"
	case strings.HasPrefix(columnType, "tinyint"):
		return "int8"
	case strings.HasPrefix(columnType, "mediumint"):
		return "int32"
	case strings.HasPrefix(columnType, "decimal"):
		return "float64"
	case strings.HasPrefix(columnType, "blob"):
		return "[]byte"
	case strings.HasPrefix(columnType, "date"):
		return "*motypes.MoTime"
	case strings.HasPrefix(columnType, "datetime"):
		return "*motypes.MoTime"
	default:
		return ""
	}
}

// PostgreSQL 数据类型转换
func convertPostgresToGoType(columnType string) string {
	switch {
	case strings.HasPrefix(columnType, "serial"):
		return "int"
	case strings.HasPrefix(columnType, "bigserial"):
		return "int64"
	case strings.HasPrefix(columnType, "numeric"):
		return "float64"
	case strings.HasPrefix(columnType, "bytea"):
		return "[]byte"
	case strings.HasPrefix(columnType, "date"):
		return "*motypes.MoTime"
	default:
		return ""
	}
}

func genTplBufferByModuleAggregateFile(filePath string, codeGeneratorModuleAggregateItemResp *tool2.CodeGeneratorModuleAggregateItemResp) (*TplFileBuffer, error) {
	if len(codeGeneratorModuleAggregateItemResp.CodeGenerators) == 0 {
		return nil, nil
	}
	tplFileBuffer := &TplFileBuffer{
		TplFileName: filepath.Base(filePath),
	}
	tplFileBuffer.TplFileType = consts.TplFileName_Type_Map[tplFileBuffer.TplFileName]
	tplFileBuffer.PackagePath = codeGeneratorModuleAggregateItemResp.CodeGenerators[0].TableDetailResp.GetPackagePathDefault()
	tplFileBuffer.ModuleName = codeGeneratorModuleAggregateItemResp.CodeGenerators[0].TableDetailResp.ModuleName
	tplFileBuffer.GenOutFileNameFromTplFileName("")
	err := getTplBuffer(tplFileBuffer, filePath, codeGeneratorModuleAggregateItemResp)
	if err != nil {
		return nil, err
	}
	return tplFileBuffer, nil
}
func genTplBufferByAggregateFile(filePath string, codeGeneratorAggregateResp *tool2.CodeGeneratorAggregateResp) (*TplFileBuffer, error) {
	tplFileBuffer := &TplFileBuffer{
		TplFileName: filepath.Base(filePath),
	}
	tplFileBuffer.GetPackagePathForAggregate()
	tplFileBuffer.TplFileType = consts.TplFileName_Type_Map[tplFileBuffer.TplFileName]
	tplFileBuffer.GenOutFileNameFromTplFileName("")
	err := getTplBuffer(tplFileBuffer, filePath, codeGeneratorAggregateResp)
	if err != nil {
		return nil, err
	}
	return tplFileBuffer, nil
}
func genTplBufferByFile(filePath string, codeGeneratorResp *tool2.CodeGeneratorResp) (*TplFileBuffer, error) {
	tplFileBuffer := &TplFileBuffer{
		TplFileName: filepath.Base(filePath),
	}
	tplFileBuffer.PackagePath = codeGeneratorResp.TableDetailResp.GetPackagePathDefault()
	tplFileBuffer.TplFileType = consts.TplFileName_Type_Map[tplFileBuffer.TplFileName]
	tplFileBuffer.ModuleName = codeGeneratorResp.TableDetailResp.ModuleName
	tplFileBuffer.TablePrefix = codeGeneratorResp.TableDetailResp.TablePrefix
	tplFileBuffer.IgnoreTablePrefix = codeGeneratorResp.IgnoreTablePrefix
	tplFileBuffer.StructName = codeGeneratorResp.TableDetailResp.StructName
	tplFileBuffer.GenOutFileNameFromTplFileName(codeGeneratorResp.TableDetailResp.SourceTableName)
	err := getTplBuffer(tplFileBuffer, filePath, codeGeneratorResp)
	if err != nil {
		return nil, err
	}
	return tplFileBuffer, nil
}
func getTplBuffer(tplFileBuffer *TplFileBuffer, filePath string, tplData any) error {
	tpl, err := template.New(tplFileBuffer.TplFileName).Funcs(MoTplFuncMap).ParseFiles(filePath)
	if err != nil {
		return err
	}
	buf := new(bytes.Buffer)
	err = tpl.Execute(buf, tplData)
	if err != nil {
		return err
	}
	tplFileBuffer.Buffer = buf
	return nil
}

// 参数:
//
//	genTable: 包含数据库表信息
//
// 返回值:
//
//	返回生成的函数名字符串，如果表注释为空则返回空字符串
//
// 说明:
//
//	此函数旨在简化表注释到函数名的转换，提高代码生成的自动化程度
func functionNameByGenTable(genTable *tool.GenTable) string {
	if genTable.TableComment == "" {
		return ""
	}
	return strings.ReplaceAll(genTable.TableComment, "表", " ")
}

func businessNameByGenTable(genTable *tool.GenTable) string {
	caser := cases.Title(language.Und)
	s := strings.Replace(genTable.SourceTableName, genTable.TablePrefix+"_", "", 1)
	return caser.String(s)
}
func tranDatasourceAddReqToPersistentDb(datasourcePogo *DatasourcePogo) (persistentDb *properties_db.PersistentDb, err error) {
	persistentDb = new(properties_db.PersistentDb)
	persistentDb.DbType = reqMapPersistentDbType[datasourcePogo.DatasourceType]

	switch persistentDb.DbType {
	case "mysql":
		tranDatasourceAddReqToGeneralDb(datasourcePogo, &persistentDb.Mysql.GeneralDB)
	default:
		err = moerrors.NewWithMsg("invalid db type:" + datasourcePogo.DatasourceType)
	}
	return
}

func tranDatasourceAddReqToGeneralDb(datasourcePogo *DatasourcePogo, generalDb *properties_db.GeneralDB) {
	generalDb.Port = strconv.FormatInt(int64(datasourcePogo.Port), 10)
	generalDb.Dbname = datasourcePogo.DbName
	generalDb.Username = datasourcePogo.DbUsername
	generalDb.Password = datasourcePogo.DbPasswd
	generalDb.Config = datasourcePogo.DbParam
	generalDb.Path = datasourcePogo.ServerAddr
	generalDb.Singular = true
	generalDb.LogZap = false
	generalDb.LogMode = "info"
}
